﻿using Exortech.NetReflector;
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using ThoughtWorks.CruiseControl.Core;
using ThoughtWorks.CruiseControl.Core.Tasks;
using ThoughtWorks.CruiseControl.Core.Util;

namespace DevEnvXml
{
    [ReflectorType("devenvxml")]
    public class DevenvTask : TaskBase
    {
        private int buildTimeoutSecons;
        private string buildType;
        private string configuration;
        private ProcessPriorityClass priority;
        private string project;
        private string solutionFile;

        public const int DEFAULT_BUILD_TIMEOUT = 600;
        public const string DEFAULT_BUILDTYPE = "rebuild";
        public const ProcessPriorityClass DEFAULT_PRIORITY = ProcessPriorityClass.Normal;
        public const string DEFAULT_PROJECT = "";
        public const string DEVENV_EXE = "devenv.com";
        private string executable;
        private readonly ProcessExecutor executor;
        private readonly string[] ExpectedVisualStudioVersions;
        public readonly Guid LogFileId;
        public const string LogFilename = "devenv-results-{0}.xml";
        private readonly IRegistry registry;
        private readonly string[] RegistryScanOrder;
        private string version;
        public const string VS_REGISTRY_KEY = "InstallDir";
        public const string VS2002_REGISTRY_PATH = @"Software\Microsoft\VisualStudio\7.0";
        public const string VS2003_REGISTRY_PATH = @"Software\Microsoft\VisualStudio\7.1";
        public const string VS2005_REGISTRY_PATH = @"Software\Microsoft\VisualStudio\8.0";
        public const string VS2008_REGISTRY_PATH = @"Software\Microsoft\VisualStudio\9.0";
        public const string VS2010_REGISTRY_PATH = @"Software\Microsoft\VisualStudio\10.0";

        public DevenvTask() : this(new Registry(), new ProcessExecutor())
        {
        }

        public DevenvTask(IRegistry registry, ProcessExecutor executor)
        {
            this.LogFileId = Guid.NewGuid();
            this.ExpectedVisualStudioVersions = new string[]
                                                    {
                                                        "10.0",
                                                        "9.0",
                                                        "8.0",
                                                        "7.1",
                                                        "7.0",
                                                        "VS2010",
                                                        "VS2008",
                                                        "VS2005",
                                                        "VS2003",
                                                        "VS2002"
                                                    };
            this.RegistryScanOrder = new string[]
                                         {
                                             @"Software\Microsoft\VisualStudio\10.0",
                                             @"Software\Microsoft\VisualStudio\9.0",
                                             @"Software\Microsoft\VisualStudio\8.0",
                                             @"Software\Microsoft\VisualStudio\7.1",
                                             @"Software\Microsoft\VisualStudio\7.0"
                                         };
            this.registry = registry;
            this.executor = executor;
            this.BuildTimeoutSeconds = 600;
            this.BuildType = "rebuild";
            this.Project = "";
            this.Priority = ProcessPriorityClass.Normal;
        }

        private string DevEnvOutputFile(IIntegrationResult result)
        {
            return Path.Combine(result.ArtifactDirectory,
                                string.Format(CultureInfo.CurrentCulture, "devenv-results-{0}.xml",LogFileId));
        }

        protected override bool Execute(IIntegrationResult result)
        {
            string information = !string.IsNullOrEmpty(base.Description)
                                     ? base.Description
                                     : string.Format(CultureInfo.CurrentCulture, "Executing Devenv :{0}",
                                                     this.GetArguments(result));

            result.BuildProgressInformation.SignalStartRunTask(information);
            ProcessResult compilerResult = TryToRun(result);
            string path = DevEnvOutputFile(result);
            if (File.Exists(path))
            {
                FileTaskResult result3 = new FileTaskResult(path);
                result3.WrapInCData = true;
                result.AddTaskResult(result3);
            }
            result.AddTaskResult(new DevenvXmlTaskResult(compilerResult));
            Log.Info("Devenv build complete.  Status: " + result.Status);
            //throw new ThoughtWorks.CruiseControl.Core.CruiseControlException();
            //throw new System.ApplicationException();
            if (compilerResult.TimedOut)
            {
                string message = string.Format(CultureInfo.CurrentCulture,
                                               "Devenv process timed out after {0} seconds.",
                                               this.BuildTimeoutSeconds);
                throw new BuilderException(this,  message);
            }
            return !compilerResult.Failed;
        }

        private string GetArguments(IIntegrationResult result)
        {
            StringBuilder builder = new StringBuilder();
            if (this.SolutionFile.StartsWith("\""))
            {
                builder.Append(this.SolutionFile);
            }
            else
            {
                builder.AppendFormat(CultureInfo.CurrentCulture, "\"{0}\"",  SolutionFile );
            }
            builder.AppendFormat(CultureInfo.CurrentCulture, " /{0}",  BuildType );
            if (this.Configuration.StartsWith("\""))
            {
                builder.AppendFormat(CultureInfo.CurrentCulture, " {0}", Configuration );
            }
            else
            {
                builder.AppendFormat(CultureInfo.CurrentCulture, " \"{0}\"", Configuration );
            }
            if (!string.IsNullOrEmpty(this.Project))
            {
                if (this.Project.StartsWith("\""))
                {
                    builder.AppendFormat(CultureInfo.CurrentCulture, " /project {0}", Project );
                }
                else
                {
                    builder.AppendFormat(CultureInfo.CurrentCulture, " /project \"{0}\"", new object[] { Project });
                }
            }
            builder.AppendFormat(CultureInfo.CurrentCulture, " /out \"{0}\"", new object[] { DevEnvOutputFile(result) });
            return builder.ToString();
        }

        private string ReadDevenvExecutableFromRegistry()
        {
            string expectedLocalMachineSubKeyValue;
            switch (this.Version)
            {
                case "VS2010":
                case "10.0":
                    expectedLocalMachineSubKeyValue =
                        this.registry.GetExpectedLocalMachineSubKeyValue(@"Software\Microsoft\VisualStudio\10.0",
                                                                         "InstallDir");
                    break;

                case "VS2008":
                case "9.0":
                    expectedLocalMachineSubKeyValue =
                        this.registry.GetExpectedLocalMachineSubKeyValue(@"Software\Microsoft\VisualStudio\9.0",
                                                                         "InstallDir");
                    break;

                case "VS2005":
                case "8.0":
                    expectedLocalMachineSubKeyValue =
                        this.registry.GetExpectedLocalMachineSubKeyValue(@"Software\Microsoft\VisualStudio\8.0",
                                                                         "InstallDir");
                    break;

                case "VS2003":
                case "7.1":
                    expectedLocalMachineSubKeyValue =
                        this.registry.GetExpectedLocalMachineSubKeyValue(@"Software\Microsoft\VisualStudio\7.1",
                                                                         "InstallDir");
                    break;

                case "VS2002":
                case "7.0":
                    expectedLocalMachineSubKeyValue =
                        this.registry.GetExpectedLocalMachineSubKeyValue(@"Software\Microsoft\VisualStudio\7.0",
                                                                         "InstallDir");
                    break;

                case null:
                    return Path.Combine(this.ScanForRegistryForVersion(), "devenv.com");

                default:
                    throw new CruiseControlException("Unknown version of Visual Studio.");
            }
            return Path.Combine(expectedLocalMachineSubKeyValue, "devenv.com");
        }

        private string ScanForRegistryForVersion()
        {
            foreach (string str in this.RegistryScanOrder)
            {
                string localMachineSubKeyValue = this.registry.GetLocalMachineSubKeyValue(str, "InstallDir");
                if (localMachineSubKeyValue != null)
                {
                    return localMachineSubKeyValue;
                }
            }
            throw new CruiseControlException("Unknown version of Visual Studio, or no version found.");
        }

        private ProcessResult TryToRun(IIntegrationResult result)
        {
            ProcessResult result2;
            ProcessInfo processInfo =
                new ProcessInfo(this.Executable, this.GetArguments(result),
                                result.WorkingDirectory, this.Priority);

            processInfo.TimeOut = this.BuildTimeoutSeconds * 1000;
            IDictionary integrationProperties = result.IntegrationProperties;
            foreach (string str in integrationProperties.Keys)
            {
                processInfo.EnvironmentVariables[str] = StringUtil.IntegrationPropertyToString(integrationProperties[str]);
            }
            Log.Info(string.Format(CultureInfo.CurrentCulture,
                                   "Starting build: {0} {1}",
                                   new object[]
                                       {
                                           processInfo.FileName,
                                           processInfo.PublicArguments
                                       }));
            try
            {
                result2 = this.executor.Execute(processInfo);
            }
            catch (IOException exception)
            {
                string message = string.Format(CultureInfo.CurrentCulture,
                                               "Unable to launch the devenv process.  Please verify that you can invoke this command from the command line: {0} {1}",
                                               new object[] {processInfo.FileName, processInfo.PublicArguments});

                throw new BuilderException(this, message, exception);
            }
            return result2;
        }

        [ReflectorProperty("buildTimeoutSeconds", Required=false)]
        public int BuildTimeoutSeconds
        {
            get
            {
                return this.buildTimeoutSecons;
            }
            set
            {
                this.buildTimeoutSecons = value;
            }
        }

        [ReflectorProperty("buildtype", Required=false)]
        public string BuildType
        {
            get
            {
                return this.buildType;
            }
            set
            {
                this.buildType = value;
            }
        }

        [ReflectorProperty("configuration")]
        public string Configuration
        {
            get
            {
                return this.configuration;
            }
            set
            {
                this.configuration = value;
            }
        }

        [ReflectorProperty("executable", Required=false)]
        public string Executable
        {
            get
            {
                if (this.executable == null)
                {
                    this.executable = this.ReadDevenvExecutableFromRegistry();
                }
                return this.executable;
            }
            set
            {
                this.executable = value;
            }
        }

        [ReflectorProperty("priority", Required=false)]
        public ProcessPriorityClass Priority
        {
            get
            {
                return this.priority;
            }
            set
            {
                this.priority = value;
            }
        }

        [ReflectorProperty("project", Required=false)]
        public string Project
        {
            get
            {
                return this.project;
            }
            set
            {
                this.project = value;
            }
        }

        [ReflectorProperty("solutionfile")]
        public string SolutionFile
        {
            get
            {
                return this.solutionFile;
            }
            set
            {
                this.solutionFile = value;
            }
        }

        [ReflectorProperty("version", Required=false)]
        public string Version
        {
            get
            {
                return this.version;
            }
            set
            {
                if (Array.IndexOf<string>(this.ExpectedVisualStudioVersions, value) == -1)
                {
                    throw new CruiseControlException("Invalid value for Version, expected one of: " +
                                                     StringUtil.Join(", ", this.ExpectedVisualStudioVersions));
                }
                this.version = value;
            }
        }
    }
}

